/*
 * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package com.sun.dtv.lwuit.plaf;

import com.sun.dtv.lwuit.*;
import com.sun.dtv.lwuit.events.StyleListener;
import java.util.Enumeration;
import java.util.Vector;

/**
 * Represents the look of a given component: colors, fonts, transperncy, margin and padding &amp; images.
 * <p>Each Component contains a Style Object and allows Style modification in Runtime
 * by Using {@code cmp.getStyle()}
 * The style is also used in Themeing, when a Theme is Changed the Styles Objects are been 
 * updated automatically.
 * <p>When changing a theme the elements changed manually in a style will not be updated
 * by the theme change by default. There are two ways to change that behavior:
 * <ol><li>Use the set method that accepts a second boolean argument and set it to true.
 * <li>Create a new style object and pass all the options in the constructor (without invoking setters manually).
 * </ol>
 * <p>
 * The Margin and Padding is inspired by <a href="http://www.w3.org/TR/REC-CSS2/box.html">W3 Box Model</a>
 *  
 *<pre>
 *
 *       **************************
 *       *         Margin         *
 *       *  ********************  *
 *       *  *      Padding     *  *
 *       *  *    ***********   *  *
 *       *  *    * Content *   *  *
 *       *  *    ***********   *  *
 *       *  *      Padding     *  *
 *       *  ********************  *
 *       *         Margin         *
 *       **************************
 *</pre> 
 * @author Chen Fishbein
 */
public class Style {
    /**
     * Background color attribute name for the theme hashtable 
     */
    public static final String BG_COLOR = "bgColor";

    /**
     * Foreground color attribute name for the theme hashtable 
     */
    public static final String FG_COLOR = "fgColor";

    /**
     * Background image attribute name for the theme hashtable 
     */
    public static final String BG_IMAGE = "bgImage";
    
    /**
     * Background selection color attribute name for the theme hashtable 
     */
    public static final String BG_SELECTION_COLOR = "bgSelectionColor";

    /**
     * Foreground color attribute name for the theme hashtable 
     */
    public static final String FG_SELECTION_COLOR = "fgSelectionColor";
    
    /**
     * Font attribute name for the theme hashtable 
     */
    public static final String FONT = "font";

    /**
     * Scaled image attribute name for the theme hashtable 
     */
    public static final String SCALED_IMAGE = "scaledImage";

    /**
     * Transparency attribute name for the theme hashtable 
     */
    public static final String TRANSPARENCY = "transparency";

    /**
     * Margin attribute name for the theme hashtable 
     */
    public static final String MARGIN = "margin";

    /**
     * Border attribute name for the theme hashtable 
     */
    public static final String BORDER = "border";

    /**
     * Padding attribute name for the theme hashtable 
     */
    public static final String PADDING = "padding";
    private int fgColor = 0x000000;
    private int bgColor = 0xFFFFFF;
    private int bgSelectionColor = 0xCCFFCC;
    private int fgSelectionColor = 0xFFCCCC;
    private Font font = Font.getDefaultFont();
    private Image bgImage;
    private int[] padding = new int[4];
    private int[] margin = new int[4];
    private byte transparency = (byte) 0xFF; //no transparency
    private Painter bgPainter;
    
    private Border border = null;
    
    /**
     * Indicates whether the bg image should be scaled or tiled defaults to scaled
     */
    private boolean scaleImage = true;
    /**
     * The modified flag indicates which portions of the style have changed using
     * bitmask values
     */
    private short modifiedFlag;
    /**
     * Used for modified flag
     */
    private static final short FG_COLOR_MODIFIED = 1;
    /**
     * Used for modified flag
     */
    private static final short BG_COLOR_MODIFIED = 2;
    /**
     * Used for modified flag
     */
    private static final short FG_SELECTION_MODIFIED = 4;
    /**
     * Used for modified flag
     */
    private static final short BG_SELECTION_MODIFIED = 8;
    /**
     * Used for modified flag
     */
    private static final short FONT_MODIFIED = 16;
    /**
     * Used for modified flag
     */
    private static final short BG_IMAGE_MODIFIED = 32;
    /**
     * Used for modified flag
     */
    private static final short SCALE_IMAGE_MODIFIED = 64;
    /**
     * Used for modified flag
     */
    private static final short TRANSPARENCY_MODIFIED = 128;
    /**
     * Used for modified flag
     */
    private static final short PADDING_MODIFIED = 256;
    /**
     * Used for modified flag
     */
    private static final short MARGIN_MODIFIED = 512;

    /**
     * Used for modified flag
     */
    private static final short BORDER_MODIFIED = 1024;

    private Vector listeners;

    /**
     * Each component when it draw itself uses this Object 
     * to determine in what colors it should use.
     * When a Component is generated it construct a default Style Object.
     * The Default values for each Component can be changed by using the UIManager class
     */
    public Style() {
        setPadding(3, 3, 3, 3);
        setMargin(2, 2, 2, 2);
        modifiedFlag = 0;
    }

    /**
     * Creates a full copy of the given style. Notice that if the original style was modified 
     * manually (by invoking setters on it) it would not chnage when changing a theme/look and feel,
     * however this newly created style would change in such a case.
     */
    public Style(Style style) {
        this(style.getFgColor(), style.getBgColor(), style.getFgSelectionColor(), style.getBgSelectionColor(), style.getFont(), style.getBgTransparency(),
                style.getBgImage(), style.isScaleImage());
        setPadding(style.getPadding(Component.TOP),
                style.getPadding(Component.BOTTOM),
                style.getPadding(Component.LEFT),
                style.getPadding(Component.RIGHT));
        setMargin(style.getMargin(Component.TOP),
                style.getMargin(Component.BOTTOM),
                style.getMargin(Component.LEFT),
                style.getMargin(Component.RIGHT));
        setBorder(style.getBorder());
        modifiedFlag = 0;
    }

    /**
     * Creates a new style with the given attributes
     */
    public Style(int fgColor, int bgColor, int fgSelectionColor, int bgSelectionColor, Font f, byte transparency) {
        this(fgColor, bgColor, fgSelectionColor, bgSelectionColor, f, transparency, null, false);
    }

    /**
     * Creates a new style with the given attributes
     */
    public Style(int fgColor, int bgColor, int fgSelectionColor, int bgSelectionColor, Font f, byte transparency, Image im, boolean scaledImage) {
        this();
        this.fgColor = fgColor;
        this.bgColor = bgColor;
        this.font = f;
        this.bgSelectionColor = bgSelectionColor;
        this.fgSelectionColor = fgSelectionColor;
        this.transparency = transparency;
        this.scaleImage = scaledImage;
        this.bgImage = im;
        
    }

    /**
     * Merges the new style with the current style without changing the elements that
     * were modified.
     * 
     * @param style new values of styles from the current theme
     */
    public void merge(Style style) {
        short tmp = modifiedFlag;

        if ((modifiedFlag & FG_COLOR_MODIFIED) == 0) {
            setFgColor(style.getFgColor());
        }
        if ((modifiedFlag & BG_COLOR_MODIFIED) == 0) {
            setBgColor(style.getBgColor());
        }
        if ((modifiedFlag & BG_IMAGE_MODIFIED) == 0) {
            setBgImage(style.getBgImage());
        }
        if ((modifiedFlag & FONT_MODIFIED) == 0) {
            setFont(style.getFont());
        }
        if ((modifiedFlag & BG_SELECTION_MODIFIED) == 0) {
            setBgSelectionColor(style.getBgSelectionColor());
        }
        if ((modifiedFlag & FG_SELECTION_MODIFIED) == 0) {
            setFgSelectionColor(style.getFgSelectionColor());
        }
        if ((modifiedFlag & SCALE_IMAGE_MODIFIED) == 0) {
            setScaleImage(style.isScaleImage());
        }

        if ((modifiedFlag & TRANSPARENCY_MODIFIED) == 0) {
            setBgTransparency(style.getBgTransparency());
        }

        if ((modifiedFlag & PADDING_MODIFIED) == 0) {
            setPadding(style.getPadding(Component.TOP),
                    style.getPadding(Component.BOTTOM),
                    style.getPadding(Component.LEFT),
                    style.getPadding(Component.RIGHT));
        }

        if ((modifiedFlag & MARGIN_MODIFIED) == 0) {
            setMargin(style.getMargin(Component.TOP),
                    style.getMargin(Component.BOTTOM),
                    style.getMargin(Component.LEFT),
                    style.getMargin(Component.RIGHT));
        }
        
        if ((modifiedFlag & BORDER_MODIFIED) == 0) {
            setBorder(style.getBorder());
        }

        modifiedFlag = tmp;
    }

    /**
     * Returns true if the style was modified manually after it was created by the
     * look and feel. If the style was modified manually (by one of the set methods)
     * then it should be merged rather than overwritten.
     */
    public boolean isModified() {
        return modifiedFlag != 0;
    }

    /**
     * @return the background color for the component
     */
    public int getBgColor() {
        return bgColor;
    }

    /**
     * @return the background image for the component
     */
    public Image getBgImage() {
        return bgImage;
    }

    /**
     * @return the foreground color for the component
     */
    public int getFgColor() {
        return fgColor;
    }

    /**
     * @return the font for the component
     */
    public Font getFont() {
        return font;
    }

    /**
     * Sets the background color for the component
     * 
     * @param bgColor RRGGBB color that ignors the alpha component
     */
    public void setBgColor(int bgColor) {
        setBgColor(bgColor, false);
    }

    /**
     * Sets the background image for the component
     */
    public void setBgImage(Image bgImage) {
        setBgImage(bgImage, false);
    }

    /**
     * Sets the foreground color for the component
     */
    public void setFgColor(int fgColor) {
        setFgColor(fgColor, false);
    }

    /**
     * Sets the font for the component
     */
    public void setFont(Font font) {
        setFont(font, false);
    }

    /**
     * @return the background selection color for the component
     */
    public int getBgSelectionColor() {
        return bgSelectionColor;
    }

    /**
     * Sets the background selection color for the component
     */
    public void setBgSelectionColor(int bgSelectionColor) {
        setBgSelectionColor(bgSelectionColor, false);
    }

    /**
     * @return the foreground selection color for the component
     */
    public int getFgSelectionColor() {
        return fgSelectionColor;
    }

    /**
     * Sets the foreground selection color for the component
     */
    public void setFgSelectionColor(int fgSelectionColor) {
        setFgSelectionColor(fgSelectionColor, false);
    }

    /**
     * @return true if the image in the background is scalled, false if it is tiled
     */
    public boolean isScaleImage() {
        return scaleImage;
    }

    /**
     * Set to true if the image in the background is scalled, false if it is tiled
     */
    public void setScaleImage(boolean scaleImage) {
        setScaleImage(scaleImage, false);
    }

    /**
     *
     * @return  the transparency level of the Component
     *
     */
    public byte getBgTransparency() {
        if(bgImage != null && (bgImage.isAnimation() || bgImage.isOpaque())) {
            return (byte)0xff;
        }
        return transparency;
    }

    /**
     * Sets the Component transparency level.
     * @param transparency transparency level as byte
     */
    public void setBgTransparency(byte transparency) {
        setBgTransparency(transparency & 0xFF, false);
    }

    /**
     * Sets the Component transparency level. Valid values should be a 
     * number between 0-255
     * @param transparency int value between 0-255
     */
    public void setBgTransparency(int transparency) {
        setBgTransparency(transparency, false);
    }

    /**
     * Sets the Style Padding
     *  
     * @param top number of pixels to padd
     * @param bottom number of pixels to padd
     * @param left number of pixels to padd
     * @param right number of pixels to padd
     */
    public void setPadding(int top, int bottom, int left, int right) {
        if (top < 0 || left < 0 || right < 0 || bottom < 0) {
            throw new IllegalArgumentException("padding cannot be negative");
        }
        padding[Component.TOP] = top;
        padding[Component.BOTTOM] = bottom;
        padding[Component.LEFT] = left;
        padding[Component.RIGHT] = right;

        modifiedFlag |= PADDING_MODIFIED;
        firePropertyChanged(PADDING);
    }

    /**
     * Sets the Style Padding
     * 
     * @param orientation one of: Component.TOP, Component.BOTTOM, Component.LEFT, Component.RIGHT
     * @param gap number of pixels to padd
     */
    public void setPadding(int orientation, int gap) {
        setPadding(orientation, gap, false);
    }

    /**
     * Sets the Style Margin
     *  
     * @param top number of margin pixels
     * @param bottom number of margin pixels
     * @param left number of margin pixels
     * @param right number of margin pixels
     */
    public void setMargin(int top, int bottom, int left, int right) {
        if (top < 0 || left < 0 || right < 0 || bottom < 0) {
            throw new IllegalArgumentException("margin cannot be negative");
        }
        margin[Component.TOP] = top;
        margin[Component.BOTTOM] = bottom;
        margin[Component.LEFT] = left;
        margin[Component.RIGHT] = right;

        modifiedFlag |= MARGIN_MODIFIED;
        firePropertyChanged(MARGIN);
    }

    /**
     * Sets the Style Margin
     * 
     * @param orientation one of: Component.TOP, Component.BOTTOM, Component.LEFT, Component.RIGHT
     * @param gap number of margin pixels
     */
    public void setMargin(int orientation, int gap) {
        setMargin(orientation, gap, false);
    }

    /**
     * Returns the Padding
     * 
     * @param orientation one of: Component.TOP, Component.BOTTOM, Component.LEFT, Component.RIGHT
     * @return number of padding pixels in the givven orientation
     */
    public int getPadding(int orientation) {
        if (orientation < Component.TOP || orientation > Component.RIGHT) {
            throw new IllegalArgumentException("wrong orientation " + orientation);
        }
        return padding[orientation];
    }

    /**
     * Returns the Margin
     * 
     * @param orientation one of: Component.TOP, Component.BOTTOM, Component.LEFT, Component.RIGHT
     * @return number of margin pixels in the givven orientation
     */
    public int getMargin(int orientation) {
        if (orientation < Component.TOP || orientation > Component.RIGHT) {
            throw new IllegalArgumentException("wrong orientation " + orientation);
        }
        return margin[orientation];
    }

    /**
     * Sets the background color for the component
     * 
     * @param bgColor RRGGBB color that ignors the alpha component
     * @param override If set to true allows the look and feel/theme to override 
     * the value in this attribute when changing a theme/look and feel
     */
    public void setBgColor(int bgColor, boolean override) {
        this.bgColor = bgColor;
        if(!override){
            modifiedFlag |= BG_COLOR_MODIFIED;
        }
        firePropertyChanged(BG_COLOR);
    }

    /**
     * Sets the background image for the component
     * 
     * @param override If set to true allows the look and feel/theme to override 
     * the value in this attribute when changing a theme/look and feel
     */
    public void setBgImage(Image bgImage, boolean override) {
        this.bgImage = bgImage;
        if(!override){
            modifiedFlag |= BG_IMAGE_MODIFIED;
        }
        firePropertyChanged(BG_IMAGE);
    }

    /**
     * Sets the foreground color for the component
     * 
     * @param override If set to true allows the look and feel/theme to override 
     * the value in this attribute when changing a theme/look and feel
     */
    public void setFgColor(int fgColor, boolean override) {
        this.fgColor = fgColor;
        if(!override){
            modifiedFlag |= FG_COLOR_MODIFIED;
        }
        firePropertyChanged(FG_COLOR);
    }

    /**
     * Sets the font for the component
     * 
     * @param override If set to true allows the look and feel/theme to override 
     * the value in this attribute when changing a theme/look and feel
     */
    public void setFont(Font font, boolean override) {
        this.font = font;
        if(!override){
            modifiedFlag |= FONT_MODIFIED;
        }
        firePropertyChanged(FONT);
    }


    /**
     * Sets the background selection color for the component
     * 
     * @param override If set to true allows the look and feel/theme to override 
     * the value in this attribute when changing a theme/look and feel
     */
    public void setBgSelectionColor(int bgSelectionColor, boolean override) {
        this.bgSelectionColor = bgSelectionColor;
        if(!override){
            modifiedFlag |= BG_SELECTION_MODIFIED;
        }
        firePropertyChanged(BG_SELECTION_COLOR);
    }

    /**
     * Sets the foreground selection color for the component
     * 
     * @param override If set to true allows the look and feel/theme to override 
     * the value in this attribute when changing a theme/look and feel
     */
    public void setFgSelectionColor(int fgSelectionColor, boolean override) {
        this.fgSelectionColor = fgSelectionColor;
        if(!override){
            modifiedFlag |= FG_SELECTION_MODIFIED;
        }
        firePropertyChanged(FG_SELECTION_COLOR);
    }

    /**
     * Set to true if the image in the background is scalled, false if it is tiled
     * 
     * @param override If set to true allows the look and feel/theme to override 
     * the value in this attribute when changing a theme/look and feel
     */
    public void setScaleImage(boolean scaleImage, boolean override) {
        this.scaleImage = scaleImage;
        if(!override){
            modifiedFlag |= SCALE_IMAGE_MODIFIED;
        }
        firePropertyChanged(SCALED_IMAGE);
    }

    /**
     * Sets the Component transparency level. Valid values should be a 
     * number between 0-255
     * 
     * @param transparency int value between 0-255
     */
    public void setBgTransparency(int transparency, boolean override) {
        if (transparency < 0 || transparency > 255) {
            throw new IllegalArgumentException("valid values are between 0-255");
        }
        this.transparency = (byte) transparency;
        
        if(!override){
            modifiedFlag |= TRANSPARENCY_MODIFIED;
        }
        firePropertyChanged(TRANSPARENCY);
    }


    /**
     * Sets the Style Padding
     * 
     * @param orientation one of: Component.TOP, Component.BOTTOM, Component.LEFT, Component.RIGHT
     * @param gap number of pixels to padd
     * @param override If set to true allows the look and feel/theme to override 
     * the value in this attribute when changing a theme/look and feel
     */
    public void setPadding(int orientation, int gap,boolean override) {
        if (orientation < Component.TOP || orientation > Component.RIGHT) {
            throw new IllegalArgumentException("wrong orientation " + orientation);
        }
        if (gap < 0) {
            throw new IllegalArgumentException("padding cannot be negative");
        }
        padding[orientation] = gap;

        if(!override){
            modifiedFlag |= PADDING_MODIFIED;
        }
        firePropertyChanged(PADDING);
    }


    /**
     * Sets the Style Margin
     * 
     * @param orientation one of: Component.TOP, Component.BOTTOM, Component.LEFT, Component.RIGHT
     * @param gap number of margin pixels
     * @param override If set to true allows the look and feel/theme to override 
     * the value in this attribute when changing a theme/look and feel
     */
    public void setMargin(int orientation, int gap, boolean override) {
        if (orientation < Component.TOP || orientation > Component.RIGHT) {
            throw new IllegalArgumentException("wrong orientation " + orientation);
        }
        if (gap < 0) {
            throw new IllegalArgumentException("margin cannot be negative");
        }
        margin[orientation] = gap;
        if(!override){
            modifiedFlag |= MARGIN_MODIFIED;
        }
        firePropertyChanged(MARGIN);
    }

    
    private void firePropertyChanged(String propertName) {
        if (listeners == null) {
            return;
        }
        Enumeration enums = listeners.elements();
        while (enums.hasMoreElements()) {
            StyleListener l = (StyleListener) enums.nextElement();
            l.styleChanged(propertName, this);
        }
    }

    /**
     * Adds a Style Listener to the Style Object.
     * 
     * @param l a style listener
     */
    public void addStyleListener(StyleListener l) {
        if (listeners == null) {
            listeners = new Vector();
        }
        listeners.addElement(l);
    }

    /**
     * Removes a Style Listener from the Style Object.
     * 
     * @param l a style listener
     */
    public void removeStyleListener(StyleListener l) {
        if (listeners != null) {
            listeners.removeElement(l);
        }
    }

    void resetModifiedFlag() {
        modifiedFlag = 0;
    }

    /**
     * Sets the border for the style
     * 
     * @param border new border object for the component
     */
    public void setBorder(Border border) {
        this.border = border;
        modifiedFlag |= BORDER_MODIFIED;
        firePropertyChanged(BORDER);
    }

    /**
     * Sets the border for the style
     * 
     * @param border new border object for the component
     * @param override If set to true allows the look and feel/theme to override 
     * the value in this attribute when changing a theme/look and feel
     */
    public void setBorder(Border border, boolean override) {
        this.border = border;
        if(!override){
            modifiedFlag |= BORDER_MODIFIED;
        }
        firePropertyChanged(BORDER);
    }
    
    /**
     * Returns the border for the style
     */
    public Border getBorder() {
        return border;
    }
    
    /**
     * Return the background painter for this style, normally this would be 
     * the internal image/color painter but can be user defined 
     */
    public Painter getBgPainter() {
        return bgPainter;
    }

    /**
     * Defines the background painter for this style, normally this would be 
     * the internal image/color painter but can be user defined 
     * 
     * @param bgPainter new painter to install into the style
     */
    public void setBgPainter(Painter bgPainter) {
        this.bgPainter = bgPainter;
    }

}
